package com.iyao_app.preview;

/**
 * 作者 :  胡家兵
 * 时间 :  2017/11/27.
 */
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Build.VERSION;
import android.support.annotation.Nullable;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.widget.ScrollerCompat;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.View.OnLongClickListener;
import android.view.View.OnTouchListener;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;
import com.facebook.drawee.drawable.ScalingUtils.ScaleType;
import com.facebook.drawee.generic.GenericDraweeHierarchy;
import com.facebook.drawee.view.DraweeView;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;



public class Attacher implements IAttacher, OnTouchListener, OnScaleDragGestureListener {
    private static final int EDGE_NONE = -1;
    private static final int EDGE_LEFT = 0;
    private static final int EDGE_TOP = 0;
    private static final int EDGE_RIGHT = 1;
    private static final int EDGE_BOTTOM = 1;
    private static final int EDGE_BOTH = 2;
    public static final int HORIZONTAL = 0;
    public static final int VERTICAL = 1;
    private int mOrientation = 0;
    private final float[] mMatrixValues = new float[9];
    private final RectF mDisplayRect = new RectF();
    private final Interpolator mZoomInterpolator = new AccelerateDecelerateInterpolator();
    private float mMinScale = 1.0F;
    private float mMidScale = 1.75F;
    private float mMaxScale = 3.0F;
    private long mZoomDuration = 200L;
    private ScaleDragDetector mScaleDragDetector;
    private GestureDetectorCompat mGestureDetector;
    private boolean mBlockParentIntercept = false;
    private boolean mAllowParentInterceptOnEdge = true;
    private int mScrollEdgeX = 2;
    private int mScrollEdgeY = 2;
    private final Matrix mMatrix = new Matrix();
    private int mImageInfoHeight = -1;
    private int mImageInfoWidth = -1;
    private Attacher.FlingRunnable mCurrentFlingRunnable;
    private WeakReference<DraweeView<GenericDraweeHierarchy>> mDraweeView;
    private OnPhotoTapListener mPhotoTapListener;
    private OnViewTapListener mViewTapListener;
    private OnLongClickListener mLongClickListener;
    private OnScaleChangeListener mScaleChangeListener;

    public Attacher(DraweeView<GenericDraweeHierarchy> draweeView) {
        this.mDraweeView = new WeakReference(draweeView);
        ((GenericDraweeHierarchy)draweeView.getHierarchy()).setActualImageScaleType(ScaleType.FIT_CENTER);
        draweeView.setOnTouchListener(this);
        this.mScaleDragDetector = new ScaleDragDetector(draweeView.getContext(), this);
        this.mGestureDetector = new GestureDetectorCompat(draweeView.getContext(), new SimpleOnGestureListener() {
            public void onLongPress(MotionEvent e) {
                super.onLongPress(e);
                if(null != Attacher.this.mLongClickListener) {
                    Attacher.this.mLongClickListener.onLongClick(Attacher.this.getDraweeView());
                }

            }
        });
        this.mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
    }

    public void setOnDoubleTapListener(OnDoubleTapListener newOnDoubleTapListener) {
        if(newOnDoubleTapListener != null) {
            this.mGestureDetector.setOnDoubleTapListener(newOnDoubleTapListener);
        } else {
            this.mGestureDetector.setOnDoubleTapListener(new DefaultOnDoubleTapListener(this));
        }

    }

    @Nullable
    public DraweeView<GenericDraweeHierarchy> getDraweeView() {
        return (DraweeView)this.mDraweeView.get();
    }

    public float getMinimumScale() {
        return this.mMinScale;
    }

    public float getMediumScale() {
        return this.mMidScale;
    }

    public float getMaximumScale() {
        return this.mMaxScale;
    }

    public void setMaximumScale(float maximumScale) {
        checkZoomLevels(this.mMinScale, this.mMidScale, maximumScale);
        this.mMaxScale = maximumScale;
    }

    public void setMediumScale(float mediumScale) {
        checkZoomLevels(this.mMinScale, mediumScale, this.mMaxScale);
        this.mMidScale = mediumScale;
    }

    public void setMinimumScale(float minimumScale) {
        checkZoomLevels(minimumScale, this.mMidScale, this.mMaxScale);
        this.mMinScale = minimumScale;
    }

    public float getScale() {
        return (float)Math.sqrt((double)((float)Math.pow((double)this.getMatrixValue(this.mMatrix, 0), 2.0D) + (float)Math.pow((double)this.getMatrixValue(this.mMatrix, 3), 2.0D)));
    }

    public void setScale(float scale) {
        this.setScale(scale, false);
    }

    public void setScale(float scale, boolean animate) {
        DraweeView<GenericDraweeHierarchy> draweeView = this.getDraweeView();
        if(draweeView != null) {
            this.setScale(scale, (float)(draweeView.getRight() / 2), (float)(draweeView.getBottom() / 2), false);
        }

    }

    public void setScale(float scale, float focalX, float focalY, boolean animate) {
        DraweeView<GenericDraweeHierarchy> draweeView = this.getDraweeView();
        if(draweeView != null && scale >= this.mMinScale && scale <= this.mMaxScale) {
            if(animate) {
                draweeView.post(new Attacher.AnimatedZoomRunnable(this.getScale(), scale, focalX, focalY));
            } else {
                this.mMatrix.setScale(scale, scale, focalX, focalY);
                this.checkMatrixAndInvalidate();
            }

        }
    }

    public void setOrientation(int orientation) {
        this.mOrientation = orientation;
    }

    public void setZoomTransitionDuration(long duration) {
        duration = duration < 0L?200L:duration;
        this.mZoomDuration = duration;
    }

    public void setAllowParentInterceptOnEdge(boolean allow) {
        this.mAllowParentInterceptOnEdge = allow;
    }

    public void setOnScaleChangeListener(OnScaleChangeListener listener) {
        this.mScaleChangeListener = listener;
    }

    public void setOnLongClickListener(OnLongClickListener listener) {
        this.mLongClickListener = listener;
    }

    public void setOnPhotoTapListener(OnPhotoTapListener listener) {
        this.mPhotoTapListener = listener;
    }

    public void setOnViewTapListener(OnViewTapListener listener) {
        this.mViewTapListener = listener;
    }

    public OnPhotoTapListener getOnPhotoTapListener() {
        return this.mPhotoTapListener;
    }

    public OnViewTapListener getOnViewTapListener() {
        return this.mViewTapListener;
    }

    public void update(int imageInfoWidth, int imageInfoHeight) {
        this.mImageInfoWidth = imageInfoWidth;
        this.mImageInfoHeight = imageInfoHeight;
        this.updateBaseMatrix();
    }

    private static void checkZoomLevels(float minZoom, float midZoom, float maxZoom) {
        if(minZoom >= midZoom) {
            throw new IllegalArgumentException("MinZoom has to be less than MidZoom");
        } else if(midZoom >= maxZoom) {
            throw new IllegalArgumentException("MidZoom has to be less than MaxZoom");
        }
    }

    private int getViewWidth() {
        DraweeView<GenericDraweeHierarchy> draweeView = this.getDraweeView();
        return draweeView != null?draweeView.getWidth() - draweeView.getPaddingLeft() - draweeView.getPaddingRight():0;
    }

    private int getViewHeight() {
        DraweeView<GenericDraweeHierarchy> draweeView = this.getDraweeView();
        return draweeView != null?draweeView.getHeight() - draweeView.getPaddingTop() - draweeView.getPaddingBottom():0;
    }

    private float getMatrixValue(Matrix matrix, int whichValue) {
        matrix.getValues(this.mMatrixValues);
        return this.mMatrixValues[whichValue];
    }

    public Matrix getDrawMatrix() {
        return this.mMatrix;
    }

    public RectF getDisplayRect() {
        this.checkMatrixBounds();
        return this.getDisplayRect(this.getDrawMatrix());
    }

    public void checkMatrixAndInvalidate() {
        DraweeView<GenericDraweeHierarchy> draweeView = this.getDraweeView();
        if(draweeView != null) {
            if(this.checkMatrixBounds()) {
                draweeView.invalidate();
            }

        }
    }

    public boolean checkMatrixBounds() {
        RectF rect = this.getDisplayRect(this.getDrawMatrix());
        if(rect == null) {
            return false;
        } else {
            float height = rect.height();
            float width = rect.width();
            float deltaX = 0.0F;
            float deltaY = 0.0F;
            int viewHeight = this.getViewHeight();
            if(height <= (float)viewHeight) {
                deltaY = ((float)viewHeight - height) / 2.0F - rect.top;
                this.mScrollEdgeY = 2;
            } else if(rect.top > 0.0F) {
                deltaY = -rect.top;
                this.mScrollEdgeY = 0;
            } else if(rect.bottom < (float)viewHeight) {
                deltaY = (float)viewHeight - rect.bottom;
                this.mScrollEdgeY = 1;
            } else {
                this.mScrollEdgeY = -1;
            }

            int viewWidth = this.getViewWidth();
            if(width <= (float)viewWidth) {
                deltaX = ((float)viewWidth - width) / 2.0F - rect.left;
                this.mScrollEdgeX = 2;
            } else if(rect.left > 0.0F) {
                deltaX = -rect.left;
                this.mScrollEdgeX = 0;
            } else if(rect.right < (float)viewWidth) {
                deltaX = (float)viewWidth - rect.right;
                this.mScrollEdgeX = 1;
            } else {
                this.mScrollEdgeX = -1;
            }

            this.mMatrix.postTranslate(deltaX, deltaY);
            return true;
        }
    }

    private RectF getDisplayRect(Matrix matrix) {
        DraweeView<GenericDraweeHierarchy> draweeView = this.getDraweeView();
        if(draweeView != null && (this.mImageInfoWidth != -1 || this.mImageInfoHeight != -1)) {
            this.mDisplayRect.set(0.0F, 0.0F, (float)this.mImageInfoWidth, (float)this.mImageInfoHeight);
            ((GenericDraweeHierarchy)draweeView.getHierarchy()).getActualImageBounds(this.mDisplayRect);
            matrix.mapRect(this.mDisplayRect);
            return this.mDisplayRect;
        } else {
            return null;
        }
    }

    private void updateBaseMatrix() {
        if(this.mImageInfoWidth != -1 || this.mImageInfoHeight != -1) {
            this.resetMatrix();
        }
    }

    private void resetMatrix() {
        this.mMatrix.reset();
        this.checkMatrixBounds();
        DraweeView<GenericDraweeHierarchy> draweeView = this.getDraweeView();
        if(draweeView != null) {
            draweeView.invalidate();
        }

    }

    private void checkMinScale() {
        DraweeView<GenericDraweeHierarchy> draweeView = this.getDraweeView();
        if(draweeView != null) {
            if(this.getScale() < this.mMinScale) {
                RectF rect = this.getDisplayRect();
                if(null != rect) {
                    draweeView.post(new Attacher.AnimatedZoomRunnable(this.getScale(), this.mMinScale, rect.centerX(), rect.centerY()));
                }
            }

        }
    }

    public void onScale(float scaleFactor, float focusX, float focusY) {
        if(this.getScale() < this.mMaxScale || scaleFactor < 1.0F) {
            if(this.mScaleChangeListener != null) {
                this.mScaleChangeListener.onScaleChange(scaleFactor, focusX, focusY);
            }

            this.mMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY);
            this.checkMatrixAndInvalidate();
        }

    }

    public void onScaleEnd() {
        this.checkMinScale();
    }

    public void onDrag(float dx, float dy) {
        DraweeView<GenericDraweeHierarchy> draweeView = this.getDraweeView();
        if(draweeView != null && !this.mScaleDragDetector.isScaling()) {
            this.mMatrix.postTranslate(dx, dy);
            this.checkMatrixAndInvalidate();
            ViewParent parent = draweeView.getParent();
            if(parent == null) {
                return;
            }

            if(this.mAllowParentInterceptOnEdge && !this.mScaleDragDetector.isScaling() && !this.mBlockParentIntercept) {
                if(this.mOrientation == 0 && (this.mScrollEdgeX == 2 || this.mScrollEdgeX == 0 && dx >= 1.0F || this.mScrollEdgeX == 1 && dx <= -1.0F)) {
                    parent.requestDisallowInterceptTouchEvent(false);
                } else if(this.mOrientation == 1 && (this.mScrollEdgeY == 2 || this.mScrollEdgeY == 0 && dy >= 1.0F || this.mScrollEdgeY == 1 && dy <= -1.0F)) {
                    parent.requestDisallowInterceptTouchEvent(false);
                }
            } else {
                parent.requestDisallowInterceptTouchEvent(true);
            }
        }

    }

    public void onFling(float startX, float startY, float velocityX, float velocityY) {
        DraweeView<GenericDraweeHierarchy> draweeView = this.getDraweeView();
        if(draweeView != null) {
            this.mCurrentFlingRunnable = new Attacher.FlingRunnable(draweeView.getContext());
            this.mCurrentFlingRunnable.fling(this.getViewWidth(), this.getViewHeight(), (int)velocityX, (int)velocityY);
            draweeView.post(this.mCurrentFlingRunnable);
        }
    }

    public boolean onTouch(View v, MotionEvent event) {
        int action = MotionEventCompat.getActionMasked(event);
        ViewParent parent;
        switch(action) {
            case 0:
                parent = v.getParent();
                if(parent != null) {
                    parent.requestDisallowInterceptTouchEvent(true);
                }

                this.cancelFling();
                break;
            case 1:
            case 3:
                parent = v.getParent();
                if(parent != null) {
                    parent.requestDisallowInterceptTouchEvent(false);
                }
            case 2:
        }

        boolean wasScaling = this.mScaleDragDetector.isScaling();
        boolean wasDragging = this.mScaleDragDetector.isDragging();
        boolean handled = this.mScaleDragDetector.onTouchEvent(event);
        boolean noScale = !wasScaling && !this.mScaleDragDetector.isScaling();
        boolean noDrag = !wasDragging && !this.mScaleDragDetector.isDragging();
        this.mBlockParentIntercept = noScale && noDrag;
        if(this.mGestureDetector.onTouchEvent(event)) {
            handled = true;
        }

        return handled;
    }

    private void cancelFling() {
        if(this.mCurrentFlingRunnable != null) {
            this.mCurrentFlingRunnable.cancelFling();
            this.mCurrentFlingRunnable = null;
        }

    }

    private void postOnAnimation(View view, Runnable runnable) {
        if(VERSION.SDK_INT >= 16) {
            view.postOnAnimation(runnable);
        } else {
            view.postDelayed(runnable, 16L);
        }

    }

    protected void onDetachedFromWindow() {
        this.cancelFling();
    }

    private class FlingRunnable implements Runnable {
        private final ScrollerCompat mScroller;
        private int mCurrentX;
        private int mCurrentY;

        public FlingRunnable(Context context) {
            this.mScroller = ScrollerCompat.create(context);
        }

        public void cancelFling() {
            this.mScroller.abortAnimation();
        }

        public void fling(int viewWidth, int viewHeight, int velocityX, int velocityY) {
            RectF rect = Attacher.this.getDisplayRect();
            if(null != rect) {
                int startX = Math.round(-rect.left);
                int minX;
                int maxX;
                if((float)viewWidth < rect.width()) {
                    minX = 0;
                    maxX = Math.round(rect.width() - (float)viewWidth);
                } else {
                    maxX = startX;
                    minX = startX;
                }

                int startY = Math.round(-rect.top);
                int minY;
                int maxY;
                if((float)viewHeight < rect.height()) {
                    minY = 0;
                    maxY = Math.round(rect.height() - (float)viewHeight);
                } else {
                    maxY = startY;
                    minY = startY;
                }

                this.mCurrentX = startX;
                this.mCurrentY = startY;
                if(startX != maxX || startY != maxY) {
                    this.mScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
                }

            }
        }

        public void run() {
            if(!this.mScroller.isFinished()) {
                DraweeView<GenericDraweeHierarchy> draweeView = Attacher.this.getDraweeView();
                if(draweeView != null && this.mScroller.computeScrollOffset()) {
                    int newX = this.mScroller.getCurrX();
                    int newY = this.mScroller.getCurrY();
                    Attacher.this.mMatrix.postTranslate((float)(this.mCurrentX - newX), (float)(this.mCurrentY - newY));
                    draweeView.invalidate();
                    this.mCurrentX = newX;
                    this.mCurrentY = newY;
                    Attacher.this.postOnAnimation(draweeView, this);
                }

            }
        }
    }

    private class AnimatedZoomRunnable implements Runnable {
        private final float mFocalX;
        private final float mFocalY;
        private final long mStartTime;
        private final float mZoomStart;
        private final float mZoomEnd;

        public AnimatedZoomRunnable(float currentZoom, float targetZoom, float focalX, float focalY) {
            this.mFocalX = focalX;
            this.mFocalY = focalY;
            this.mStartTime = System.currentTimeMillis();
            this.mZoomStart = currentZoom;
            this.mZoomEnd = targetZoom;
        }

        public void run() {
            DraweeView<GenericDraweeHierarchy> draweeView = Attacher.this.getDraweeView();
            if(draweeView != null) {
                float t = this.interpolate();
                float scale = this.mZoomStart + t * (this.mZoomEnd - this.mZoomStart);
                float deltaScale = scale / Attacher.this.getScale();
                Attacher.this.onScale(deltaScale, this.mFocalX, this.mFocalY);
                if(t < 1.0F) {
                    Attacher.this.postOnAnimation(draweeView, this);
                }

            }
        }

        private float interpolate() {
            float t = 1.0F * (float)(System.currentTimeMillis() - this.mStartTime) / (float)Attacher.this.mZoomDuration;
            t = Math.min(1.0F, t);
            t = Attacher.this.mZoomInterpolator.getInterpolation(t);
            return t;
        }
    }

    @Retention(RetentionPolicy.SOURCE)
    public @interface OrientationMode {
    }
}
